package com.turnengine.client.local.action.calculator;

import static com.javabi.common.lang.Variables.notNull;
import static com.turnengine.client.local.action.enums.ActionConditionMandatory.OPTIONAL;

import java.util.List;

import com.turnengine.client.local.action.IActionCache;
import com.turnengine.client.local.action.IActionDefinition;
import com.turnengine.client.local.action.bean.IAction;
import com.turnengine.client.local.action.bean.IActionCondition;
import com.turnengine.client.local.action.bean.IActionTarget;
import com.turnengine.client.local.action.enums.ActionConditionType;
import com.turnengine.client.local.faction.IFactionCache;
import com.turnengine.client.local.group.IGroupCache;
import com.turnengine.client.local.unit.IUnitCache;
import com.turnengine.client.local.unit.bean.IUnit;
import com.turnengine.client.local.unit.enums.UnitType;
import com.turnengine.client.local.unit.list.IUnitList;
import com.turnengine.client.local.unit.storage.IStorageCache;

public class ActionCalculator implements IActionCalculator {

	private final IFactionCache factionCache;
	private final IGroupCache groupCache;
	private final IUnitCache unitCache;
	private final IActionCache actionCache;
	private final IStorageCache storageCache;

	public ActionCalculator(IFactionCache faction, IGroupCache group, IUnitCache unit, IStorageCache storage, IActionCache action) {
		this.factionCache = notNull("faction", faction);
		this.groupCache = notNull("group", group);
		this.unitCache = notNull("unit", unit);
		this.storageCache = notNull("storage", storage);
		this.actionCache = notNull("action", action);
	}

	@Override
	public IUnitCache getUnitCache() {
		return unitCache;
	}

	@Override
	public IGroupCache getGroupCache() {
		return groupCache;
	}

	@Override
	public IFactionCache getFactionCache() {
		return factionCache;
	}

	@Override
	public IActionCache getActionCache() {
		return actionCache;
	}

	@Override
	public IStorageCache getStorageCache() {
		return storageCache;
	}

	@Override
	public long getAmountCanExecute(IAction action, IUnitListSetTarget source, IUnitListSetTarget target) {
		notNull("action", action);
		notNull("source", source);

		// Get definition
		IActionDefinition definition = actionCache.getDefinition(action);
		if (definition == null) {
			throw new IllegalArgumentException("action not defined: " + action);
		}

		// Verify source type
		verifyTarget(source, definition.getSource().getType());

		// Check if target is required
		if (definition.hasTarget()) {
			if (target == null) {
				throw new IllegalArgumentException("action requires target: " + action);
			}

			// Verify target type
			verifyTarget(target, definition.getTarget().getType());
		}

		// Calculate amount (without limit)
		long amount = getAmountCanExecute(definition, source, target);

		// Apply limit
		return getLimitedAmount(definition, amount);
	}

	private long getLimitedAmount(IActionDefinition definition, long amount) {
		long limit = definition.getAction().getLimit();
		return (limit == 0) ? amount : Math.min(amount, limit);
	}

	private void verifyTarget(IUnitListSetTarget target, UnitType expected) {
		if (!target.getType().equals(expected)) {
			throw new IllegalArgumentException("expected source type: " + expected + ", actual: " + target.getType());
		}
	}

	private long getAmountCanExecute(IActionDefinition definition, IUnitListSetTarget source, IUnitListSetTarget target) {

		// Check source conditions
		long sourceAmount = getAmountCanExecute(definition.getSource(), definition.getSourceConditions(), source);
		if (!definition.hasTarget()) {
			return sourceAmount;
		}

		// Check target conditions
		long targetAmount = getAmountCanExecute(definition.getTarget(), definition.getTargetConditions(), target);
		return Math.min(sourceAmount, targetAmount);
	}

	private long getAmountCanExecute(IActionTarget actionTarget, List<IActionCondition> conditions, IUnitListSetTarget target) {
		long amount = Long.MAX_VALUE;

		for (IActionCondition condition : conditions) {
			// Optional conditions do not affect amount
			if (condition.getMandatory().equals(OPTIONAL)) {
				continue;
			}

			IUnit unit = unitCache.getById(condition.getConditionId());
			ActionConditionType type = condition.getType();

			// Player units must be matched
			IUnitList unitList = target.getListSet().getUnitList(unit.getType());

			amount = getAmountByType(amount, type, condition, unitList);
		}

		return amount;
	}

	private long getAmountByType(long amount, ActionConditionType type, IActionCondition condition, IUnitList unitList) {
		switch (type) {
			case EXISTS_ONCE:
			case REMOVE_ONCE:
				return getAmountInList(amount, condition, unitList, false);
			case EXISTS_MANY:
			case REMOVE_MANY:
				return getAmountInList(amount, condition, unitList, true);
			case ADD_ONCE:
			case ADD_MANY:
				return amount;
			default:
				throw new IllegalStateException("type=" + type);
		}
	}

	private long getAmountInList(long amount, IActionCondition condition, IUnitList unitList, boolean many) {
		int unitId = condition.getConditionId();
		long unitAmount = condition.getConditionAmount();
		if (many) {
			return Math.min(amount, unitList.count(unitId, unitAmount));
		} else {
			return unitList.contains(unitId, unitAmount) ? amount : 0;
		}
	}

}
